﻿using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Modules.Contracts.DTO;
using System.Modules.Extensions;
using System.Modules.SharePoint.Contracts.Attributes;
using System.Modules.SharePoint.Contracts.DTO;
using System.Modules.SharePoint.ExceptionHanlding;
using System.Reflection;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.Workflow;

namespace System.Modules.SharePoint.Extensions
{
    /// <summary>
    /// SP List Item Extensions
    /// </summary>
    public static class SPListItemExtensions
    {
        #region Try Get Field Value
        /// <summary>
        /// Try Getting Field Value and Handle Value does not fall within the expected range exception.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="fieldName"></param>
        /// <returns></returns>
        public static object TryGetValue(this SPListItem item, string fieldName)
        {
            object value;
            try
            {
                value = item[fieldName];
            }
            catch (Exception)
            {
                value = null;
            }
            return value;
        }
        #endregion

        #region Get Field Value
        /// <summary>
        /// Try to gets field value from field Name
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="item">SPListItem</param>
        /// <param name="fieldName">string</param>
        /// <returns></returns>
        public static T GetFieldValue<T>(this SPListItem item, string fieldName)
        {
            object value;
            Type type = typeof(T);
            try
            {
                if (item.Fields.ContainsField(fieldName))
                {
                    value = item.TryGetValue(fieldName);
                    if (value != null)
                    {
                        return (T)Convert.ChangeType(value, Nullable.GetUnderlyingType(type) ?? type);
                    }
                }
                return default(T);
            }
            catch
            {
                value = default(T);
            }
            return (T)value;
        }
        #endregion

        #region Load Item List
        /// <summary>
        /// This method Loads List of SPListItems into List of T
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection"></param>
        /// <returns></returns>
        public static List<T> LoadItemList<T>(this List<SPListItem> collection) where T : new()
        {
            return collection.Select(item => item.LoadItem<T>()).ToList();
        }

        /// <summary>
        /// This method Loads List of SPListItems into List of T
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection"></param>
        /// <returns></returns>
        public static List<T> LoadItemList<T>(this SPWorkflowTaskCollection collection) where T : new()
        {
            return (from SPWorkflowTask item in collection select item.LoadItem<T>()).ToList();
        }

        /// <summary>
        /// This method Loads List of SPListItems into List of T
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection"></param>
        /// <returns></returns>
        public static List<T> LoadItemList<T>(this SPListItemCollection collection) where T : new()
        {
            return (from SPListItem item in collection select item.LoadItem<T>()).ToList();
        }
        #endregion

        #region Load Item
        /// <summary>
        /// This method loads SPListItem into a T type.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="item">SPListItem</param>
        /// <returns>T</returns>
        public static T LoadItem<T>(this SPListItem item) where T : new()
        {
            var value = new T();
            if (item != null)
            {
                Type type = typeof(T);
                PropertyInfo[] properties = type.GetProperties();
                foreach (PropertyInfo property in properties)
                {
                    var bindingProperty = property.GetCustomAttributes(typeof(BindingSPField), false).FirstOrDefault() as BindingSPField;
                    string propertyName = bindingProperty != null && !String.IsNullOrEmpty(bindingProperty.Name) ? bindingProperty.Name : property.Name;
                    if (item.Fields.ContainsField(propertyName) && property.CanWrite)
                    {
                        SPField field = item.Fields.TryGetFieldByStaticName(propertyName);
                        if (field != null)
                        {
                            object fieldValue = item.TryGetValue(propertyName);
                            if (bindingProperty != null && bindingProperty.Function != null)
                            {
                                fieldValue = bindingProperty.Function.Invoke(fieldValue);
                            }
                            if (fieldValue != null)
                            {
                                Type valueType = fieldValue.GetType();
                                if (!field.InternalName.Equals(BaseListItem.FieldNames.UniqueId.ToString(), StringComparison.InvariantCultureIgnoreCase))
                                {
                                    string stringValue = (valueType == typeof(DateTime)) ? SPUtility.CreateISO8601DateTimeFromSystemDateTime(Convert.ToDateTime(fieldValue)) : fieldValue.ToString();
                                    fieldValue = field.GetFieldValue(stringValue);
                                }
                                if (valueType == property.PropertyType)
                                {
                                    property.SetValue(value, fieldValue, null);
                                }
                                else if (property.PropertyType.IsGenericType && property.PropertyType.IsSubclassOf(typeof(Updatable)))
                                {
                                    var updatable = Activator.CreateInstance(property.PropertyType) as Updatable;
                                    if (updatable != null)
                                    {
                                        updatable.SetValue(Convert.ChangeType(fieldValue, updatable.GetUnderlyingType()));
                                        property.SetValue(value, updatable, null);
                                    }
                                }
                                else
                                {
                                    property.SetValue(value, Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType), null);
                                }
                            }
                        }
                    }
                }
            }
            return value;
        }
        #endregion

        #region Bind Item
        /// <summary>
        /// This method Binds an object of Type T into SPListItem.
        /// SPListItem.Update() is required.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="item">SPListItem</param>
        /// <param name="value"></param>
        public static void BindItem<T>(this SPListItem item, T value)
        {
            if (item != null && !Equals(default(T), value))
            {
                Type type = typeof(T);
                PropertyInfo[] properties = type.GetProperties();
                foreach (PropertyInfo property in properties)
                {
                    var bindingProperty = property.GetCustomAttributes(typeof(BindingSPField), false).FirstOrDefault() as BindingSPField;
                    string propertyName = bindingProperty != null && !String.IsNullOrEmpty(bindingProperty.Name) ? bindingProperty.Name : property.Name;
                    if (item.Fields.ContainsField(propertyName) && property.CanRead)
                    {
                        SPField field = item.Fields.GetFieldByInternalName(propertyName);
                        if (!field.ReadOnlyField)
                        {
                            if (property.PropertyType.IsGenericType && property.PropertyType.IsSubclassOf(typeof(Updatable)))
                            {
                                var updatable = property.GetValue(value, null) as Updatable;
                                if (updatable != null && updatable.IsUpdated)
                                {
                                    item[propertyName] = updatable.GetValue();
                                }
                            }
                            else
                            {
                                item[propertyName] = property.GetValue(value, null);
                            }
                        }
                    }
                }
            }
        }
        #endregion

        #region To List
        /// <summary>
        /// Cast SPList Item Data table into List of SPListItems
        /// </summary>
        /// <param name="collection">DataTable</param>
        /// <param name="site">SPSite</param>
        /// <returns></returns>
        public static List<SPListItem> ToList(this DataTable collection, SPSite site)
        {
            return collection.Rows.Cast<DataRow>().Select(row => row.LoadItem(site)).Where(item => item != null).ToList();
        }
        #endregion

        #region Load Item
        /// <summary>
        /// Loads SPlistItem from data row
        /// </summary>
        /// <param name="item">DataRow</param>
        /// <param name="site">SPSite</param>
        /// <returns></returns>
        public static SPListItem LoadItem(this DataRow item, SPSite site)
        {
            var value = new SPSiteDataQueryBaseRow();
            Type type = typeof(SPSiteDataQueryBaseRow);
            PropertyInfo[] properties = type.GetProperties();
            foreach (PropertyInfo property in properties)
            {
                string propertyName = property.Name;
                if (item.Table.Columns.Contains(propertyName) && property.CanWrite)
                {
                    object fieldValue = item[propertyName];
                    if (fieldValue != null)
                    {
                        property.SetValue(value, Convert.ChangeType(fieldValue, property.PropertyType), null);
                    }
                }
            }
            if (site != null)
            {
                if (!value.WebId.IsNullOrEmpty() && !value.ListId.IsNullOrEmpty() && !value.ID.IsNullOrEmpty())
                {
                    using (SPWeb web = site.OpenWeb(new Guid(value.WebId)))
                    {
                        SPList list = web.Lists.GetList(new Guid(value.ListId), true);
                        if (list != null)
                        {
                            int id;
                            if (int.TryParse(value.ID, out id))
                            {
                                return list.GetItemById(id);
                            }
                        }
                    }
                }
            }
            return null;
        }
        #endregion

        #region Get Workflow
        /// <summary>
        /// Get Workflow By State
        /// </summary>
        /// <param name="item"></param>
        /// <param name="state"></param>
        /// <returns></returns>
        public static List<SPWorkflow> GetWorkflowByState(this SPListItem item, SPWorkflowState state)
        {
            return item != null ? item.Workflows.Cast<SPWorkflow>().Where(match => match.InternalState == state).ToList() : new List<SPWorkflow>();
        }

        /// <summary>
        /// Get Workflow By State
        /// </summary>
        /// <param name="item"></param>
        /// <param name="state"></param>
        /// <returns></returns>
        public static List<SPWorkflow> GetWorkflowByState(this SPListItem item, int state)
        {
            return item != null ? item.Workflows.Cast<SPWorkflow>().Where(match => (int)match.InternalState == state).ToList() : new List<SPWorkflow>();
        }

        /// <summary>
        /// Get Running Workflow
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public static SPWorkflow GetRunningWorkflow(this SPListItem item)
        {
            var states = new List<int> { 2, 10, 12, 66, 194, 1026, 1028, 2050, 5122, 4098 };
            return item != null ? item.Workflows.Cast<SPWorkflow>().FirstOrDefault(match => states.Contains((int)match.InternalState)) : null;
        }

        /// <summary>
        /// Get Workflow By Instance Id
        /// </summary>
        /// <param name="item"></param>
        /// <param name="instanceId"> </param>
        /// <returns></returns>
        public static SPWorkflow GetWorkflow(this SPListItem item, Guid instanceId)
        {
            return item != null ? item.Workflows.Cast<SPWorkflow>().FirstOrDefault(match => match.InstanceId.Equals(instanceId)) : null;
        }
        #endregion

        #region Set Item Role Assignment
        /// <summary>
        /// Set Item Role Assignment
        /// </summary>
        /// <param name="item"></param>
        /// <param name="userLogin"></param>
        /// <param name="roleType"></param>
        public static void SetItemRoleAssignment(this SPListItem item, String userLogin, SPRoleType roleType)
        {
            item.SetItemRoleAssignment(userLogin, roleType, true);
        }

        /// <summary>
        /// Set Item Role Assignment
        /// </summary>
        /// <param name="item"></param>
        /// <param name="userLogin"></param>
        /// <param name="roleType"></param>
        /// <param name="copyRoleAssignments">TRUE | Default </param>
        public static void SetItemRoleAssignment(this SPListItem item, String userLogin, SPRoleType roleType, bool copyRoleAssignments)
        {
            if (item != null && !String.IsNullOrEmpty(userLogin))
            {
                SPWeb web = item.Web.Site.RootWeb;
                SPUser user = item.Web.Site.RootWeb.EnsureUser(userLogin);
                if (user != null && web != null)
                {
                    var userRoleAssignment = new SPRoleAssignment(user);
                    SPRoleDefinition userRoleDefinition = web.RoleDefinitions.GetByType(roleType);
                    if (userRoleDefinition != null)
                    {
                        userRoleAssignment.RoleDefinitionBindings.Add(userRoleDefinition);
                        try
                        {
                            if (!item.HasUniqueRoleAssignments)
                            {
                                item.BreakRoleInheritance(copyRoleAssignments);
                            }
                            item.RoleAssignments.Add(userRoleAssignment);
                            item.SystemUpdate();
                        }
                        catch (Exception exception)
                        {
                            exception.Handle();
                        }
                    }
                }
            }
        }
        #endregion
    }
}